package net.ermannofranco.xml.schema.validation;

import java.util.LinkedList;
import java.util.List;

import net.ermannofranco.xml.schema.Schema;
import net.ermannofranco.xml.schema.SchemaException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.dom.DOMInputImpl;
import org.apache.xerces.xs.XSImplementation;
import org.apache.xerces.xs.XSLoader;
import org.apache.xerces.xs.XSModel;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.LSInput;

public class XsdValidator {
	private final transient Log log = LogFactory.getLog(getClass());
	protected int errors;

	private XSLoader schemaLoader;
	private final List<ErrorListener> listeners = new LinkedList<ErrorListener>();
	private boolean unregisterDefaultErrorListener = false;
	private final ErrorListener logErrorListener = new LogErrorListener();

	public XsdValidator() {
		// get DOM Implementation using DOM Registry
		System.setProperty(DOMImplementationRegistry.PROPERTY,
				"org.apache.xerces.dom.DOMXSImplementationSourceImpl");
		DOMImplementationRegistry registry;
		try {
			registry = DOMImplementationRegistry.newInstance();

			XSImplementation impl = (XSImplementation) registry
					.getDOMImplementation("XS-Loader");

			schemaLoader = impl.createXSLoader(null);
			DOMConfiguration config = schemaLoader.getConfig();

			// create Error Handler
			DOMErrorHandler errorHandler = new CountErrorHandler();

			// set error handler
			config.setParameter("error-handler", errorHandler);

			// set validation feature
			config.setParameter("validate", Boolean.TRUE);

		} catch (Exception e) {
			log.error(e, e);
			throw new SchemaException(e.getMessage());
		}

	}

	public void unregisterDefaultErrorListener() {
		unregisterDefaultErrorListener = true;
	}

	public void addErrorListener(ErrorListener el) {
		listeners.add(el);
	}

	public void removeErrorListener(ErrorListener el) {
		listeners.remove(el);
	}

	public void validate(Schema data) {
		String clazzName = data.getClass().getSimpleName();
		log.info(clazzName+": starting validation");
		errors = 0;

		if (!unregisterDefaultErrorListener
				&& !listeners.contains(logErrorListener)) {
			addErrorListener(logErrorListener);
		}

		try {
			// parse document

			LSInput lsi = new DOMInputImpl(null, null, null, data.toPrint(),
					"UTF-8");
			XSModel model = schemaLoader.load(lsi);
			if (model != null && errors == 0) {
				log.info(clazzName+": OK");

			} else if (errors > 0)
				log.info(clazzName+": there are " + errors + " error(s).");
			else
				log.info(clazzName+": KO");

		} catch (Exception ex) {
			ex.printStackTrace();
		}
//		log.info(data.getClass().getSimpleName()+": end validation");
	}

	private final class LogErrorListener implements ErrorListener {

		public void errorRaised(SchemaErrorEvent e) {
			short severity = e.getSeverity();
			if (severity == DOMError.SEVERITY_ERROR) {
				log.error("[xs-error]: " + e.getMessage());
			}

			else if (severity == DOMError.SEVERITY_WARNING) {
				log.warn("[xs-warning]: " + e.getMessage());
			}

			else if (severity == DOMError.SEVERITY_FATAL_ERROR) {
				log.fatal("[xs-fatal]: " + e.getMessage());
			}
		}

	}

	private final class CountErrorHandler implements DOMErrorHandler {

		public boolean handleError(DOMError error) {
			short severity = error.getSeverity();
			if (severity == DOMError.SEVERITY_ERROR) {
				errors++;
			}

			// else if (severity == DOMError.SEVERITY_WARNING) {
			// log.warn("[xs-warning]: " + error.getMessage());
			// }

			else if (severity == DOMError.SEVERITY_FATAL_ERROR) {

				errors++;
			}
			SchemaErrorEvent e = new SchemaErrorEvent(error);
			for (int i = listeners.size() - 1; i >= 0; i--) {
				listeners.get(i).errorRaised(e);
			}
			return true;

		}
	}

	public int getErrors() {
		return errors;
	}
}
